<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>双向数据绑定</title>
</head>

<body>
  <ol>
    <li>元素1</li>
    <!-- <li style="display:none;">元素2</li> -->
    <li style="visibility:hidden">元素2</li>
    <li>元素3</li>
    <li>元素4</li>
  </ol>
  <div id="app">
    <input type="text" v-model="text">
    <h3 v-model="text"></h3>
    {{ text }}
  </div>
  <script>
    function defineReactive(obj, key, val) {

      var dep = new Dep();

      Object.defineProperty(obj, key, {
        get: function () {
          if(Dep.target) dep.addSub(Dep.target)
          return val;
        },
        set: function (newVal) {  // 只要data中的属性的值发生变化，就会触发set
          if (val === newVal) return
          val = newVal;
          dep.notify();
          console.log(val);
        }
      })
    }


    // 首先我们为每个vue属性(data中的属性)用Object.defineProperty()实现数据劫持，为每个属性分配一个收集所有订阅者的管理数组dep；
    function observe(obj, vm) {
      Object.keys(obj).forEach(function (key) {
        // key 为data对象中的属性名 obj[key]为属性对应的值
        defineReactive(vm, key, obj[key]);
      })
    }

    // console.log(dom);
    // document.getElementById("app").appendChild(dom);

    // 然后在编译的时候在该属性的数组dep中添加订阅者，v-model会添加一个订阅者，{{}}也会，v-bind也会，只要用到该属性的指令理论上都会；
    function compile(node, vm) {
      var reg = /\{\{(.*)\}\}/;
      if (node.nodeType === 1) {// 结点类型为元素
        var attr = node.attributes;
        for (var i = 0; i < attr.length; i++) {
          if (attr[i].nodeName == "v-model") {
            var name = attr[i].nodeValue;// 获取v-mode的属性名
            node.addEventListener("input", function (e) {
              vm[name] = e.target.value;
            })
            node.value = vm[name];
            node.removeAttribute("v-model");
          }
        }
        new Watch(vm, node, name, 'text');
      }

      if (node.nodeType === 3) {// 结点类型为文本
        if (reg.test(node.nodeValue)) {
          var name = RegExp.$1;// 获取匹配到的{{}}中的字符串
          name = name.trim();

          // node.nodeValue = vm[name];
          new Watch(vm, node, name, 'text');
        }
      }
    }

    function Watch(vm, node, name, nodeType) {
      Dep.target = this;
      this.vm = vm;
      this.node = node;
      this.name = name;
      this.nodeType = nodeType;
      this.update();
      Dep.target = null;
    }
    Watch.prototype = {
      update: function () {
        this.get()
        if (this.nodeType === 'text') {
          this.node.nodeValue = this.value;
        }
        if (this.nodeType === 'input') {
          this.node.value = this.value;
        }
      },
      get: function () {
        this.value = this.vm[this.name];
      }
    }

    function Dep() {
      this.subs = [];
    }
    Dep.prototype = {
      addSub: function (sub) {
        this.subs.push(sub);
      },

      notify: function () {
        this.subs.forEach(function (item) {
          item.update();
        })
      }
    }


    function nodeToFragment(node, vm) {
      var flag = document.createDocumentFragment();
      var child;
      while (child = node.firstChild) {
        console.log(child);
        compile(child, vm)
        flag.appendChild(child);
      }
      return flag;
    }
    function Vue(options) {
      this.data = options.data;
      var data = this.data;

      observe(data, this);


      var id = options.el;
      var dom = nodeToFragment(document.getElementById(id), this);
      document.getElementById("app").appendChild(dom);
    }
    var vm = new Vue({
      el: "app",
      data: {
        text: "Hello wor"
      }
    })


    // 面试回答：https://www.cnblogs.com/webcabana/p/11077628.html
    /*
    　Vue是采用数据劫持结合发布/订阅模式的方式，通过Object.defineProperty()来劫持各个属性的setter，getter，在数据变动时发布消息给订阅者，触发相应的监听回调。

    首先我们为每个vue属性(data中的属性)用Object.defineProperty()实现数据劫持，为每个属性分配一个收集所有订阅者的管理数组dep；
    
    然后在编译的时候在该属性的数组dep中添加订阅者，v-model会添加一个订阅者，{{}}也会，v-bind也会，只要用到该属性的指令理论上都会；
    接着为input会添加监听事件，修改值就等于为该属性赋值，则会触发该属性的set方法，在set方法内通知订阅者数组dep，订阅者数组循环调用各订阅者的update方法更新视图。
    */


  </script>

</body>

</html>